<?php
/**
 * Ly - 系统模板引擎类
 *
 * 提供模板解析功能，含模块解析和插件解析
 *
 * Copyright (c) 2013 YinHailin All rights reserved.
 * @Author: YinHailin
 * @Authorize: Boren Network
 * @Created by YinHailin at 2013-11-01
 */

defined('LY_VERSION') or exit();

class TemplateCore {

  /* 引擎配置 */
  private $Conf = array();

  /* 模板变量 */
  private $TempVar = array();

  /* 架构方法 */
  public function __construct() {
    $this->Conf['cache_path'] = RUNTIME_PATH.'Cache/';
    $this->Conf['cache_suffix'] = C('TEMP_CACHFILE_SUFFIX');
    $this->Conf['temp_tag_begin'] = C('TEMP_TAG_BEGIN');
    $this->Conf['temp_tag_end'] = C('TEMP_TAG_END');
    $this->Conf['taglib_begin'] = C('TAGLIB_BEGIN');
    $this->Conf['taglib_end'] = C('TAGLIB_END');
  }

  /*
   * 加载模板
   * @param string $tempFile 模板文件
   * @param string $tempVar 模板变量
   * @param string $prefix  模板缓存文件前缀
   * @return string
   */
  public function Fetch($tempFile = '', $tempVar = '', $prefix = '') {
    $this->TempVar = $tempVar;
    //模板阵列变量分解成为独立变量
    extract($this->TempVar, EXTR_OVERWRITE);
    //加载并缓存模板
    $cacheTempFile = $this->_LoadTempFile($tempFile, $prefix);
    return $cacheTempFile;
  }

  /*
   * 加载并缓存模板
   * @param string $tempFile 模板文件
   * @param string $prefix  模板缓存文件前缀
   * @return string
   */
  private function _LoadTempFile($tempFile, $prefix = '') {
    $cacheTempFile = $this->Conf['cache_path'].$prefix.md5($tempFile).$this->Conf['cache_suffix'];
    if (APP_DEBUG || !is_file($cacheTempFile)) {
      if (!is_file($tempFile)) { halt('模板文件'.$tempFile.'不存在', __FILE__, __LINE__);}
      //读取模板内容
      $tempContent = file_get_contents($tempFile);
      //编译模板内容
      $cacheTempContent = $this->Compile($tempContent);
      //检查模板缓存目录是否存在，不存在则尝试创建
      $cacheTempDir = dirname($cacheTempFile);
      if (!is_dir($cacheTempDir)) mkdir($cacheTempDir, 0755, true);
      //写入Cache文件
      $result = file_put_contents($cacheTempFile, trim($cacheTempContent));
      if ($result === false) halt('缓存文件写入失败', __FILE__, __LINE__);
    }
    return $cacheTempFile;
  }

  /*
   * 编译内容
   * @param string $tempFile 模板文件
   * @param string $prefix  模板缓存文件前缀
   * @return string
   */
  public function Compile($tempContent) {
    //内容解析
    $tempContent = $this->Parse($tempContent);
    $tempContent = strip_whitespace($tempContent);
    return $tempContent;
  }

  /*
   * 内容解析
   * @param string $tempContent 模板内容
   * @return string
   */
  public function Parse($tempContent) {
    //模板include标签解析
    $tempContent = $this->ParseInclude($tempContent);
    //模板插槽功能
    $tempContent = $this->ParseRabbet($tempContent);
    //PHP语法解析
    $tempContent = $this->ParsePhp($tempContent);
    //标签库标签解析
    $taglibs = explode(',', C('TAGLIB_LOAD'));
    foreach ($taglibs as $taglib) {
      $tempContent = $this->ParseTaglib($taglib, $tempContent);
    }
    //用户自定义标签解析
    $tempContent = $this->ParseUserTag($tempContent);
    //普通标签解析
    $tempContent = preg_replace('/('.$this->Conf['temp_tag_begin'].')([^\d\s'.$this->Conf['temp_tag_begin'].$this->Conf['temp_tag_end'].'].+?)('.$this->Conf['temp_tag_end'].')/eis', "\$this->ParseTag('\\2')", $tempContent);
    return $tempContent;
  }

  /*
   * 模板include标签解析
   * @param string $tempContent 模板内容
   * @return string
   */
  public function ParseInclude($tempContent) {
    //读取模板中的include标签
    $find = preg_match_all('/'.$this->Conf['taglib_begin'].'include\s(.+?)\s*\/'.$this->Conf['taglib_end'].'/is', $tempContent, $tags);
    if ($find) {
      foreach ($tags[1] as $key => $tag) {
        //分析参数
        $args = $this->ParseXmlAttr($tag);
        $tempContent = str_replace($tags[0][$key], $this->_LoadIncludeFile($args['file']), $tempContent);
      }
    }
    return $tempContent;
  }

  /*
   * 载入公共文件
   * @param string $file 公共文件
   * @return string
   */
  private function _LoadIncludeFile($file) {
    $includeFile = $this->_LocationTemplate($file);
    if (is_file($includeFile)) {
      $fileContent = file_get_contents($includeFile);
      return $this->ParseInclude($fileContent);
    } else {
      return '';
    }
  }

  /*
   * 获取模板文件真实路径
   * @param string $templateFile 模板文件
   * @return string
   */
  private function _LocationTemplate($templateFile) {
    if (strpos($templateFile, '.')) {
      //相对于项目的全路径
      $templateFile = TPL_PATH.$templateFile;
    } elseif (strpos($templateFile, ':')) {
      //模块名：行为名模式
      $templateFile = str_replace(':', '/', $templateFile);
      $templateFile = TPL_PATH.$templateFile.C('TEMP_TEMPLATE_SUFFIX');
    } else {
      //行为名模式
      $templateFile = TPL_PATH.MODULE_NAME.'/'.$templateFile.C('TEMP_TEMPLATE_SUFFIX');
    }
    return $templateFile;
  }

  /*
   * 模板插槽功能：rabbet标签解析
   * @param string $tempContent 模板内容
   * @return string
   */
  public function ParseRabbet($tempContent) {
    //读取模板中的rabbet标签
    $find = preg_match_all('/'.$this->Conf['taglib_begin'].'rabbet\s(.+?)\s*\/'.$this->Conf['taglib_end'].'/is', $tempContent, $tags);
    if ($find) {
      foreach ($tags[1] as $key => $tag) {
        //分析参数
        $args = $this->ParseXmlAttr($tag);
        $tempContent = str_replace($tags[0][$key], $this->_LoadRabbetFile($args['id']), $tempContent);
      }
    }
    return $tempContent;
  }

  /*
   * 载入插槽文件
   * @param string $file 公共文件
   * @return string
   */
  private function _LoadRabbetFile($id) {
    $rabbetList = rabbet('template_file', $id);  //获取插槽文件列表
    $fileContent = '';
    foreach ($rabbetList as $file) {
      if (is_file($file)) {
        $fileContent .= file_get_contents($file);
      }
    }
    return $this->ParseRabbet($fileContent);
  }

  /*
   * XML属性解析
   + 将传递的字符串作为XML的属性进行分析
   * @param string $attrs XML属性字符串
   * @return array
   */
  public function ParseXmlAttr($attrs) {
    $filterTrue = array('&', '>', '<');
    $filterFalse = array('*and*', '*gt*', '*lt*');
    //XML安全过滤
    $attrs = str_replace($filterTrue, $filterFalse, $attrs);
    $xml = '<tpl><tag '.$attrs.' /></tpl>';
    $xml = simplexml_load_string($xml);
    if (!$xml) {
      halt('XML标签属性有误', __FILE__, __LINE__);
    } else {
      $args = (array)($xml->tag->attributes());
      $args = array_change_key_case($args['@attributes'], CASE_LOWER);
      foreach ($args as $key => $arg) {
        $args[$key] = str_replace($filterFalse, $filterTrue, $args[$key]);
      }
      return $args;
    }
  }

  /*
   * PHP语法解析
   * @param string $tempContent 模板内容
   * @return string
   */
  public function ParsePhp($tempContent) {
    if (ini_get('short_open_tag')) {
      // 开启短标签的情况要将<?标签用echo方式输出 否则无法正常输出xml标识
      $tempContent = preg_replace('/(<\?(?!php|=|$))/i', '<?php echo \'\\1\'; ?>'."\n", $tempContent );
    }
    return $tempContent;
  }

  /*
   * 标签库标签解析
   * @param string $taglib 标签库
   * @param string $tempContent 模板内容
   * @return string
   */
  public function ParseTaglib($taglib, $tempContent) {
    $tempContent = str_replace('\\', '＼', $tempContent);
    $begin = $this->Conf['taglib_begin'];  //标签库标签开始标记
    $end = $this->Conf['taglib_end'];  //标签库标签结束标记
    $className = parse_name($taglib, 1).'Taglib';
    $objTaglib = LyCore::Instance($className);
    foreach ($objTaglib->GetTags() as $name => $var) {
      if (isset($var['alias'])) {
        //标签存在别名
        $tags = explode(',', $var['alias']);
        $tags[] = $name;
      } else {
        //标签无别名
        $tags = array($name);
      }
      $level = isset($var['level']) ? $var['level'] : 1;
      $close = isset($var['close']) ? $var['close'] : true;
      foreach ($tags as $tag) {
        $parseTag = $tag;  //实际模板中需要解析的标签
        if (!method_exists($objTaglib, '_'.$tag)) {
          $tag = $name;  //别名标签可以无解析方法
        }
        $n1 = empty($var['attr']) ? '(\s*)' : '\s*((?:"[^"]*"|\'[^\']*\'|[^\'"])*?)';
        if (!$close) {
          //非闭合标签解析
          $pattern = '/'.$begin.$parseTag.$n1.'\/(\s*)'.$end.'/eis';
          $replace = "\$this->ParseXmlTag('$taglib','$tag','$1','')";
          $tempContent = preg_replace($pattern, $replace, $tempContent);
        } else {
          //闭合标签解析
          $pattern = '/'.$begin.$parseTag.$n1.$end.'(.*?)'.$begin.'\/'.$parseTag.'(\s*)'.$end.'/eis';
          $replace = "\$this->ParseXmlTag('$taglib','$tag','$1','$2')";
          for ($i = 0; $i < $level; $i++) {
            $tempContent = preg_replace($pattern, $replace, $tempContent);
          }
        }
      }
    }
    $tempContent = str_replace('＼', '\\', $tempContent);
    return $tempContent;
  }

  /*
   * 标签库标签Xml方式解析
   * @param string $taglib 标签库
   * @param string $tag 标签名
   * @param string $attr 标签属性
   * @param string $content 模板内容
   * @return string
   */
  public function ParseXmlTag($taglib, $tag, $attr, $content) {
    $attr = stripslashes($attr);
    $content = stripslashes($content);
    if (ini_get('magic_quotes_sybase')) {
      $attr = str_replace('\"', '\'', $attr);
    }
    $objTaglib = LyCore::Instance(parse_name($taglib, 1).'Taglib');
    $func = '_'.$tag;
    return $objTaglib->$func($attr, trim($content));
  }

  /*
   * 用户自定义标签解析
   * @param string $tempContent 模板内容
   * @return string
   */
  public function ParseUserTag($tempContent) {
    $userTags = C('USER_TAGS');
    if (is_array($userTags)) {
      foreach ($userTags as $tag => $value) {
        $tempContent = str_replace($tag, $value, $tempContent);
      }
    }
    return $tempContent;
  }

  /*
   * 普通标签解析
   * @param string $tagStr 标签内容
   * @return string
   */
  public function ParseTag($tagStr) {
    $tagStr = stripslashes($tagStr);
    $flag = substr($tagStr, 0, 1);
    $name = substr($tagStr, 1);
    if ($flag == '$' && preg_match('/^[A-Za-z_].*/', $name)) {
      //解析模板变量
      return $this->ParseVar($name);
    } elseif ($flag == '(' || $flag == '+' || $flag == '-') {
      //输出计算
      return '<?php echo '.$tagStr.';?>';
    } elseif ($flag == ':') {
      //运行函数，并输出结果
      return '<?php echo '.$name.';?>';
    } elseif ($flag == '~') {
      //运行函数，不需要输出结果
      return '<?php '.$name.';?>';
    } elseif (substr($tagStr, 0, 2) == '//' || (substr($tagStr, 0, 2) == '/*' && substr($tagStr, -2) == '*/')) {
      //注释
      return '';
    }
    //非模板标签原格式返回
    return $this->Conf['temp_tag_begin'].$tagStr.$this->Conf['temp_tag_end'];
  }

  /*
   * 标签变量解析
   + 标签变量格式：变量|函数|函数=参数1,参数2,参数3
   * @param string $varStr 变量内容
   * @return string
   */
  public function ParseVar($varStr) {
    static $_parseVarList = array();
    $identifier = md5($varStr);
    //如果已解析过该变量，则直接返回该变量
    if (isset($_parseVarList[$identifier])) return $_parseVarList[$identifier];
    $varArray = explode('|', $varStr);
    $var = array_shift($varArray);
    if (strpos($var, '.') !== false) {
      //解析点语法数组
      $vars = explode('.', $var);
      $var = array_shift($vars);
      $string = '$'.$var;
      foreach ($vars as $value) {
        if (strpos($value, '$') === 0) {
          $string .= '['.$value.']';
        } else {
          $string .= '[\''.$value.'\']';
        }
      }
    } else {
      //解析普通语法
      $string = '$'.$var;
    }
    //解析对变量运用的函数
    if (count($varArray) > 0) {
      $string = $this->ParseFunction($string, $varArray);
    }
    $_parseVarList[$identifier] = '<?php echo '.$string.';?>';
    return $_parseVarList[$identifier];
  }

  /*
   * 模板变量函数解析
   + 标签变量格式：变量|函数|函数=参数1,参数2,参数3
   * @param string $varStr 变量内容
   * @param array $varArray 函数列表
   * @return string
   */
  public function ParseFunction($varStr, $varArray) {
    foreach ($varArray as $var) {
      $args = explode('=', $var, 2);
      $func = strtolower(trim(array_shift($args)));
      switch ($func) {
        case 'default' :
          //特殊模板函数，默认值
          $varStr = '('.$varStr.') ? '.$varStr.' : '.$args[0];
          break;
        default :
          if (count($args) > 0) {
            //多参数函数
            if (strstr($args[0], '###')) {
              //模板变量不是第一参数
              $args[0] = str_replace('###', $varStr, $args[0]);
              $varStr = $func.'('.$args[0].')';
            } else {
              //模板变量是第一参数
              $varStr = $func.'('.$varStr.','.$args[0].')';
            }
          } else {
            //单参数函数
            $varStr = $func.'('.$varStr.')';
          }
      }
    }
    return $varStr;
  }
}